<?php

/**
 * MysqlImport import data from mysql dump file.
 *
 */
class MysqlImport
{
	var $compression = 'none';
	var $charset_conversion = false;
	var $charset_of_file = NULL;
	var $charset = NULL;
	var $read_limit = NULL;
	var $import_file = NULL;
	var $import_text = NULL;

	var $import_handle = NULL;
	var $offset = NULL;
	var $finished = NULL;

	var $ready = false;
	var $error = NULL;

	public function __construct ($config=array(), $init=true)
	{
		$this->config = $config;
		if ($init) $this->init(true);
	} // end of __construct
	public function init ($force=false)
	{
		if ($this->ready && !$force) return $this->ready;
		foreach ($this->config as $k=>$v) {
			$this->$k = $v;
		}
		return $this->ready = $this->_init();
	} // end of method init
	public function _init ()
	{
		if (isset($this->import_file) && !isset($this->import_handle)) {
			$this->import_handle = fopen($this->import_file, 'r');
			if (!$this->import_handle) {
				$this->error = "Can't open [$this->import_file] for reading";
				return false;
			}
		}
		if (isset($this->import_text)) {
			// phpMyAdmin query reader function, PMA_importGetNextChunk,
			// can accept data from string value, using such strange method
			// (leave content unarchived :)
			$this->compression = 'application/zip';
		}
		return true;
	} // end of method _init

	public function processQuery ($sql, $raw=NULL)
	{
		if (isset($this->db)) $this->db->query($sql);
		else var_dump($sql);
	} // end of method processQuery

	const PMA_MYSQL_INT_VERSION = 50000;
	const PMA_PHP_INT_VERSION = 51000;

	/**
	 * Returns next part of imported file/buffer.
	 *
	 * lucky : Adapted from phpMyAdmin-2.11.5 : libraries/import.lib.php
	 *
	 * @uses    $GLOBALS['offset'] read and write
	 * @uses    $GLOBALS['import_file'] read only
	 * @uses    $GLOBALS['import_text'] read and write
	 * @uses    $GLOBALS['finished'] read and write
	 * @uses    $GLOBALS['read_limit'] read only
	 * @param  integer size of buffer to read (this is maximal size
	 *                  function will return)
	 * @return string part of file/buffer
	 * @access public
	 */
	function PMA_importGetNextChunk($size = 32768)
	{
			// We can not read too much
			if (isset($this->read_limit) && $size > $this->read_limit) {
					$size = $this->read_limit;
			}

			if ($this->finished) {
					return TRUE;
			}

			/*
			if ($GLOBALS['import_file'] == 'none') {
					// Well this is not yet supported and tested, but should return content of textarea
					if (strlen($GLOBALS['import_text']) < $size) {
							$GLOBALS['finished'] = TRUE;
							return $GLOBALS['import_text'];
					} else {
							$r = substr($GLOBALS['import_text'], 0, $size);
							$GLOBALS['offset'] += $size;
							$GLOBALS['import_text'] = substr($GLOBALS['import_text'], $size);
							return $r;
					}
			}
			 */

			switch ($this->compression) {
					case 'application/bzip2':
							$result = bzread($this->import_handle, $size);
							$this->finished = feof($this->import_handle);
							break;
					case 'application/gzip':
							$result = gzread($this->import_handle, $size);
							$this->finished = feof($this->import_handle);
							break;
					case 'application/zip':
							$result = substr($this->import_text, 0, $size);
							$this->import_text = substr($this->import_text, $size);
							$this->finished = empty($this->import_text);
							break;
					case 'none':
							$result = fread($this->import_handle, $size);
							$this->finished = feof($this->import_handle);
							break;
			}
			$this->offset += $size;

			if ($this->charset_conversion) {
					return PMA_convert_string($this->charset_of_file, $this->charset, $result);
			} else {
					/**
					 * Skip possible byte order marks (I do not think we need more
					 * charsets, but feel free to add more, you can use wikipedia for
					 * reference: <http://en.wikipedia.org/wiki/Byte_Order_Mark>)
					 *
					 * @todo BOM could be used for charset autodetection
					 */
					if ($this->offset == $size) {
							// UTF-8
							if (strncmp($result, "\xEF\xBB\xBF", 3) == 0) {
									$result = substr($result, 3);
							// UTF-16 BE, LE
							} elseif (strncmp($result, "\xFE\xFF", 2) == 0 || strncmp($result, "\xFF\xFE", 2) == 0) {
									$result = substr($result, 2);
							}
					}
					return $result;
			}
	}

	// lucky : Adapted from phpMyAdmin-2.11.5 : libraries/import/sql.php
	public function process($sql_delimiter=';', $sql_compatibility=NULL)
	{
		$buffer = '';
		// Defaults for parser
		$sql = '';
		$start_pos = 0;
		$i = 0;
		$len= 0;

		// Handle compatibility option
		if (isset($sql_compatibility)) {
			$this->processQuery('SET SQL_MODE="' . $sql_compatibility . '"');
		}

		while (!($this->finished && $i >= $len) && !$this->error) {
			$data = $this->PMA_importGetNextChunk();
			if ($data === FALSE) {
				// subtract data we didn't handle yet and stop processing
				$offset -= strlen($buffer);
				break;
			} elseif ($data === TRUE) {
				// Handle rest of buffer
			} else {
				// Append new data to buffer
				$buffer .= $data;
				// free memory
				unset($data);
				// Do not parse string when we're not at the end and don't have ; inside
				if ((strpos($buffer, $sql_delimiter, $i) === FALSE) && !$this->finished)  {
					continue;
				}
			}
			// Current length of our buffer
			$len = strlen($buffer);
			// prepare an uppercase copy of buffer for PHP < 5
			// outside of the loop
			if (self::PMA_PHP_INT_VERSION < 50000) {
				$buffer_upper = strtoupper($buffer);
			}
			// Grab some SQL queries out of it
			while ($i < $len) {
				$found_delimiter = false;
				// Find first interesting character, several strpos seem to be faster than simple loop in php:
				//while (($i < $len) && (strpos('\'";#-/', $buffer[$i]) === FALSE)) $i++;
				//if ($i == $len) break;
				$oi = $i;
				$big_value = 2147483647;
				$first_quote = strpos($buffer, '\'', $i);
				if ($first_quote === FALSE) {
					$first_quote = $big_value;
				}
				$p2 = strpos($buffer, '"', $i);
				if ($p2 === FALSE) {
					$p2 = $big_value;
				}
				/**
				 * @todo it's a shortcoming to look for a delimiter that might be
				 *       inside quotes (or even double-quotes)
				 */
				$first_sql_delimiter = strpos($buffer, $sql_delimiter, $i);
				if ($first_sql_delimiter === FALSE) {
					$first_sql_delimiter = $big_value;
				} else {
					$found_delimiter = true;
				}
				$p4 = strpos($buffer, '#', $i);
				if ($p4 === FALSE) {
					$p4 = $big_value;
				}
				$p5 = strpos($buffer, '--', $i);
				if ($p5 === FALSE || $p5 >= ($len - 2) || $buffer[$p5 + 2] > ' ') {
					$p5 = $big_value;
				}
				$p6 = strpos($buffer, '/*', $i);
				if ($p6 === FALSE) {
					$p6 = $big_value;
				}
				$p7 = strpos($buffer, '`', $i);
				if ($p7 === FALSE) {
					$p7 = $big_value;
				}
				// catch also "delimiter"
				if (self::PMA_PHP_INT_VERSION >= 50000) {
					$p8 = stripos($buffer, 'DELIMITER', $i);
				} else {
					$p8 = strpos($buffer_upper, 'DELIMITER', $i);
				}
				if ($p8 === FALSE || $p8 >= ($len - 11) || $buffer[$p8 + 9] > ' ') {
					$p8 = $big_value;
				}
				$i = min ($first_quote, $p2, $first_sql_delimiter, $p4, $p5, $p6, $p7, $p8);
				unset($first_quote, $p2, $p4, $p5, $p6, $p7, $p8);
				if ($i == $big_value) {
					$i = $oi;
					if (!$this->finished) {
						break;
					}
					// at the end there might be some whitespace...
					if (trim($buffer) == '') {
						$buffer = '';
						$len = 0;
						break;
					}
					// We hit end of query, go there!
					$i = strlen($buffer) - 1;
				}

				// Grab current character
				$ch = $buffer[$i];

				// Quotes
				if (strpos('\'"`', $ch) !== FALSE) {
					$quote = $ch;
					$endq = FALSE;
					while (!$endq) {
						// Find next quote
						$pos = strpos($buffer, $quote, $i + 1);
						// No quote? Too short string
						if ($pos === FALSE) {
							// We hit end of string => unclosed quote, but we handle it as end of query
							if ($this->finished) {
								$endq = TRUE;
								$i = $len - 1;
							}
							$found_delimiter = false;
							break;
						}
						// Was not the quote escaped?
						$j = $pos - 1;
						while ($buffer[$j] == '\\') $j--;
						// Even count means it was not escaped
						$endq = (((($pos - 1) - $j) % 2) == 0);
						// Skip the string
						$i = $pos;

						if ($first_sql_delimiter < $pos) {
							$found_delimiter = false;
						}
					}
					if (!$endq) {
						break;
					}
					$i++;
					// Aren't we at the end?
					if ($this->finished && $i == $len) {
						$i--;
					} else {
						continue;
					}
				}

				// Not enough data to decide
				if ((($i == ($len - 1) && ($ch == '-' || $ch == '/'))
					|| ($i == ($len - 2) && (($ch == '-' && $buffer[$i + 1] == '-')
						|| ($ch == '/' && $buffer[$i + 1] == '*')))) && !$this->finished) {
							break;
						}

				// Comments
				if ($ch == '#'
					|| ($i < ($len - 1) && $ch == '-' && $buffer[$i + 1] == '-' && (($i < ($len - 2) && $buffer[$i + 2] <= ' ') || ($i == ($len - 1) && $this->finished)))
						|| ($i < ($len - 1) && $ch == '/' && $buffer[$i + 1] == '*')
							) {
								// Copy current string to SQL
								if ($start_pos != $i) {
									$sql .= substr($buffer, $start_pos, $i - $start_pos);
								}
								// Skip the rest
								$j = $i;
								$i = strpos($buffer, $ch == '/' ? '*/' : "\n", $i);
								// didn't we hit end of string?
								if ($i === FALSE) {
									if ($this->finished) {
										$i = $len - 1;
									} else {
										break;
									}
								}
								// Skip *
								if ($ch == '/') {
									// Check for MySQL conditional comments and include them as-is
									if ($buffer[$j + 2] == '!') {
										$comment = substr($buffer, $j + 3, $i - $j - 3);
										if (preg_match('/^[0-9]{5}/', $comment, $version)) {
											if ($version[0] <= self::PMA_MYSQL_INT_VERSION) {
												$sql .= substr($comment, 5);
											}
										} else {
											$sql .= $comment;
										}
									}
									$i++;
								}
								// Skip last char
								$i++;
								// Next query part will start here
								$start_pos = $i;
								// Aren't we at the end?
								if ($i == $len) {
									$i--;
								} else {
									continue;
								}
							}
				// Change delimiter, if redefined, and skip it (don't send to server!)
				if ((strtoupper(substr($buffer, $i, 9)) == "DELIMITER") && ($buffer[$i + 9] <= ' ') && ($i<$len-11) && (!(strpos($buffer,"\n",$i+11)===FALSE))) {
					$new_line_pos = strpos($buffer, "\n", $i + 10);
					$sql_delimiter = substr($buffer, $i+10, $new_line_pos - $i -10);
					$i= $new_line_pos + 1;
					// Next query part will start here
					$start_pos = $i;
					continue;
				}

				// End of SQL
				if ($found_delimiter || ($this->finished && ($i == $len - 1))) {
					$tmp_sql = $sql;
					if ($start_pos < $len) {
						$length_to_grab = $i - $start_pos;
						if (!$found_delimiter) {
							$length_to_grab++;
						}
						$tmp_sql .= substr($buffer, $start_pos, $length_to_grab);
						unset($length_to_grab);
					}
					// Do not try to execute empty SQL
					if (!preg_match('/^([\s]*;)*$/', trim($tmp_sql))) {
						$sql = $tmp_sql;
						$this->processQuery($sql, substr($buffer, 0, $i + strlen($sql_delimiter)));
						$buffer = substr($buffer, $i + strlen($sql_delimiter));
						// Reset parser:
						$len = strlen($buffer);
						$sql = '';
						$i = 0;
						$start_pos = 0;
						// Any chance we will get a complete query?
						//if ((strpos($buffer, ';') === FALSE) && !$finished) {
						if ((strpos($buffer, $sql_delimiter) === FALSE) && !$this->finished) {
							break;
						}
					} else {
						$i++;
						$start_pos = $i;
					}
				}

			} // End of parser loop
		} // End of import loop
		// Commit any possible data in buffers
		# HACK : 2008-03-13 : lucky
		# just empty query?
		#$this->processQuery('', substr($buffer, 0, $len));
		return !$this->error;
	}

	public function updateFromDump() {
		if ( !isset($this->import_handle) || empty($this->import_handle) ) {
			$this->error = "Can't open [$this->import_file] for reading";
			return false;
		}

		while ( $str = fgets($this->import_handle) ) {
			if ( !preg_match( '/^\s*[\-\/\#]/', $str ) ) {
				$this->db->query($str);
			}
		}

		return true;
	}
}

